using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;

namespace Eclipse.TRCore.Http
{
    public class HttpPostArguments : NameObjectCollectionBase
    {
        private DictionaryEntry _de;
		private Encoding _encoding;
		private bool _isMultipart = false;
		private ArrayList _paths;
        private string _boundary;

		public HttpPostArguments()
		{
			_de = new DictionaryEntry();
			_paths = new ArrayList();
		}

		public HttpPostArguments(Encoding e): this()
		{
			_encoding = e;
		}


		// Adds elements from an IDictionary into the new collection.
        public HttpPostArguments(IDictionary d, Boolean readOnly)
            : this()
		{
			foreach (DictionaryEntry de in d)
			{
				this.BaseAdd((String) de.Key, de.Value);
			}
            this.IsReadOnly = readOnly;
		}

		// Gets a key-and-value pair (DictionaryEntry) using an index.
		public DictionaryEntry this[int index]
		{
			get
			{
				_de.Key = this.BaseGetKey(index);
				_de.Value = this.BaseGet(index);
				return (_de);
			}
		}

		// Gets or sets the value associated with the specified key.
		public Object this[String key]
		{
			get { return (this.BaseGet(key)); }
			set { this.BaseSet(key, value); }
		}

		// Gets a String array that contains all the keys in the collection.
		public String[] AllKeys
		{
			get { return (this.BaseGetAllKeys()); }
		}

		// Gets an Object array that contains all the values in the collection.
		public Array AllValues
		{
			get { return (this.BaseGetAllValues()); }
		}

		// Gets a String array that contains all the values in the collection.
		public String[] AllStringValues
		{
			get { return ((String[]) this.BaseGetAllValues(Type.GetType("System.String"))); }
		}

		// Gets a value indicating if the collection contains keys that are not null.
		public Boolean HasKeys
		{
			get { return (this.BaseHasKeys()); }
		}

		public Encoding Encoding
		{
			get { return _encoding; }
			set { _encoding = value; }
		}

		public bool HasMultipart
		{
			get { return _paths.Count != 0; }
		}

		private ArrayList InternalFilePaths
		{
			get { return  _paths; }
		}

		public ArrayList FilePaths
		{
			get {return (ArrayList) _paths.Clone();}
		}

		public bool IsMultipart
		{
			get
			{
				return _isMultipart | HasMultipart;
			}
			set { _isMultipart = value; }
		}

        public string Boundary
        {
            get{ return _boundary;}
            set{ _boundary = value;}
        }

		// Adds an entry to the collection.
		public void Add(String key, String value)
		{
			Add(key, value, false);
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="filePaths"></param>
		public void AddFiles(String key, params String[] filePaths)
		{
			Add(key, filePaths, true);
		}

		public void Add(String key, String value, bool isMultipart)
		{
			Add(key, (Object)value, isMultipart);
		}


		private void Add(String key, Object value, bool isMultipart)
		{
			if(!(value is String || value is String[]))
			throw new NotSupportedException("value type");
			if (this[key] == null)
			{
				if (isMultipart)
				{
					if(value is String)
						this.BaseAdd(key, new string[]{(string)value});
					else
						this.BaseAdd(key,value);
					InternalFilePaths.Add(key);
				}
				else
					this.BaseAdd(key,value);
			}
			else
			{
				if(isMultipart && value is String)
				{
					string[] oldpath = (string[]) this.BaseGet(key);
					string[] newpath = new string[oldpath.Length + 1];
					Array.Copy(oldpath, newpath, oldpath.Length);
					newpath[oldpath.Length] = (string) value;
					this.BaseSet(key, newpath);
				}
				else
				{
					this.BaseSet(key, value);
				}
				if(InternalFilePaths.Contains(key))
				{
					if(!isMultipart)
					{
						InternalFilePaths.Remove(key);
					}
				}
				else
				{
					if(isMultipart)
					{
						InternalFilePaths.Add(key);
					}
				}

			}
		}


		public bool Ismultipart(string key)
		{
			return InternalFilePaths.Contains(key);
		}


		// Removes an entry with the specified key from the collection.
		public void Remove(String key)
		{
			this.BaseRemove(key);
			if (InternalFilePaths.Contains(key))
			{
				InternalFilePaths.Remove(key);
			}
		}

		// Removes an entry in the specified index from the collection.
		public void Remove(int index)
		{
			if (index >= 0 && index < base.Count - 1)
				InternalFilePaths.Remove(this.Keys[index]);
			this.BaseRemoveAt(index);
		}

		// Clears all the elements in the collection.
		public void Clear()
		{
			this.BaseClear();
			InternalFilePaths.Clear();
		}

		public String BuildString()
		{
			if (IsMultipart)
				throw new WarningException("you can't use this func to build a multipart string");
			if (!BaseHasKeys() || base.Keys.Count == 0)
			{
				return string.Empty;
			}
			string text = string.Empty;
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string s in base.Keys)
			{
				if (s == string.Empty || !(this[s] is String))
					return null;
				stringBuilder.Append(text);
				stringBuilder.Append(UrlEncode(s));
				stringBuilder.Append("=");
				stringBuilder.Append(UrlEncode((String) this[s]));
				text = "&";
			}
			return stringBuilder.ToString();
		}

		public byte[] BuildByteArray()
		{
			if (IsMultipart)
				throw new WarningException("you can't use this func to build multipart bytes");
			return Encoding.ASCII.GetBytes(BuildString());
		}


//		private static readonly string  _SAFECHARS = "0123456789" +  // Numeric
//									"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +  // Alphabetic
//									"abcdefghijklmnopqrstuvwxyz" +
//									"-_.!~*'()";					// RFC2396 Mark characters
//		private string URLEncoding(string vstrin)
//		{
//			StringBuilder strreturn = new StringBuilder(); 
//			for(int i=0;i<vstrin.Length ;i++) 
//			{
//				char ch = vstrin[i];
//				if(vstrin[i] == ' ')
//					strreturn.Append('+');
//				else
//					if(_SAFECHARS.IndexOf(ch) != -1)
//					strreturn.Append(ch);
//				else
//				{
//					byte[] b=System.Text.Encoding.Default.GetBytes(vstrin.Substring(i,1));
//					foreach(byte d in b)
//					{
//						strreturn.Append("%" + d.ToString("X2"));
//					} 
//				}
//			}
//			return strreturn.ToString();
//		}

		private string UrlEncode(string str)
		{
			if (str == null)
			{
				return null;
			}
			return _encoding == null ? UrlEncode(str, Encoding.Default) : UrlEncode(str, _encoding);
		}

		private static string UrlEncode(string str, Encoding e)
		{
			if (str == null)
			{
				return null;
			}
			return Encoding.ASCII.GetString(UrlEncodeToBytes(str, e));
		}

		private static byte[] UrlEncodeToBytes(string str, Encoding e)
		{
			if (str == null)
			{
				return null;
			}
			byte[] bytes = e.GetBytes(str);
			return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
		}

		private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
		{
			int cSpaces = 0;
			int cUnsafe = 0;
			for (int i = 0; i < count; i++)
			{
				char ch = (char) bytes[offset + i];
				if (ch == ' ')
				{
					cSpaces++;
				}
				else if (!IsSafe(ch))
				{
					cUnsafe++;
				}
			}
			if ((!alwaysCreateReturnValue && (cSpaces == 0)) && (cUnsafe == 0))
			{
				return bytes;
			}
			byte[] expandedBytes = new byte[count + (cUnsafe*2)];
			int pos = 0;
			for (int i = 0; i < count; i++)
			{
				byte b = bytes[offset + i];
				char ch = (char) b;
				if (IsSafe(ch))
				{
					expandedBytes[pos++] = b;
				}
				else if (ch == ' ')
				{
					expandedBytes[pos++] = 0x2b;
				}
				else
				{
					expandedBytes[pos++] = 0x25;
					expandedBytes[pos++] = (byte) IntToHex((b >> 4) & 15);
					expandedBytes[pos++] = (byte) IntToHex(b & 15);
				}
			}
			return expandedBytes;
		}

		private static bool IsSafe(char ch)
		{
			if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')
				return true;

			switch (ch)
			{
				case '-':
				case '_':
				case '.':
				case '!':
				case '*':
				case '\'':
				case '(':
				case ')':
					return true;
			}

			return false;
		}

		private static char IntToHex(int n)
		{
			if (n <= 9)
			{
				return (char) ((ushort) (n + 0x30));	// '0'
			}
			return (char) ((ushort) ((n - 10) + 0x41)); // 'a'
		}


    }
}
